package client

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"ngrok/log"
	"os"
	"runtime"
	"strings"
	"time"
)

func getInterfaceAddr(name string) (addr string, ret bool) {
	if interfaces, err := net.Interfaces(); nil == err {
		for _, inter := range interfaces {
			if name == inter.Name {
				for _, s := range strings.Split(inter.HardwareAddr.String(), ":") {
					addr += s
				}
				ret = true
			}
		}
	}
	return
}

func getLocalAddr() (ip string) {
	addrs, err := net.InterfaceAddrs()
	if err == nil {
		var iparr []string
		for _, addr := range addrs {
			if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
				if ipnet.IP.To4() != nil {
					iparr = append(iparr, addr.String())
				}
			}
		}

		ip = strings.Join(iparr, "+")
	}

	if "" == ip || 0 >= len(ip) {
		ip = "0.0.0.0/0"
	}

	return
}

func getHashVersion() (hash string) {
	if runtime.GOOS == "linux" {
		if f, err := os.Open("/proc/self/exe"); nil == err {
			defer f.Close()
			md5Ctx := md5.New()
			io.Copy(md5Ctx, f)
			md5sum := hex.EncodeToString(md5Ctx.Sum(nil))
			if 8 < len(md5sum) {
				hash = string(md5sum[0:8])
			}
		}
	}
	return
}

func getDevName(path string) (devname string) {
	var maps map[string]interface{}
	bts, err := ioutil.ReadFile(path)
	if nil == err {
		err = json.Unmarshal(bts, &maps)
		if nil == err {
			if n := maps["ngrok"]; nil != n {
				if m, ok := n.(map[string]interface{}); ok {
					if x := m["devname"]; nil != x {
						if value := x.(string); 0 < len(value) {
							devname = value
						}
					}
				}
			}
		}
	}

	if "" == devname || 0 >= len(devname) {
		devname = "UNKOWN"
	}

	return
}

func getConfigUpdateUrl(path string) (config_url string, ret bool) {
	bts, err := ioutil.ReadFile(path)
	if nil == err {
		var maps map[string]interface{}
		err = json.Unmarshal(bts, &maps)
		if nil == err {
			if n, ok := maps["ngrok"].(map[string]interface{}); ok {
				if u := n["url"].(string); 0 < len(u) {
					if addr, ok := getInterfaceAddr("eth0"); ok {
						config_url = u + "?clientid=" + addr
						ret = true
					}
				}
			}
		}
	}

	return
}

func getJsonFromServer(url string) (data []byte, ret bool) {
	if 0 < len(url) {
		client := http.Client{
			Timeout: time.Duration(5 * time.Second),
		}
		log.Info("http client resquest: %s", url)
		if res, err := client.Get(url); nil == err {
			defer res.Body.Close()
			if 200 == res.StatusCode {
				data, err = ioutil.ReadAll(res.Body)
				if nil == err {
					log.Info(string(data))
					ret = true
				}
			}
		}
	}
	return
}

func parseJsonToConfig(data []byte, config string, hosts string) (ret bool) {
	if 0 < len(config) {
		var maps map[string]interface{}
		if err := json.Unmarshal(data, &maps); nil == err {
			if status, ok := maps["status"].(bool); ok && status {
				if response, ok := maps["response"].(map[string]interface{}); ok {
					var configBuffer bytes.Buffer
					var tunnelsBuffer bytes.Buffer
					var hostsMaps map[string]string
					for key, value := range response {
						switch value.(type) {
						case string:
							if v, ok := value.(string); ok {
								configBuffer.WriteString(fmt.Sprintf("%s: \"%s\"\r\n", key, v))
							}
						case bool:
							if v, ok := value.(bool); ok {
								configBuffer.WriteString(fmt.Sprintf("%s: \"%v\"\r\n", key, v))
							}
						case []interface{}:
							switch key {
							case "tunnels":
								tunnelsBuffer.WriteString("tunnels:" + "\r\n")
								for _, value := range value.([]interface{}) {
									if value, ok := value.(map[string]interface{}); ok {
										if name, ok := value["name"].(string); ok {
											if proto, ok := value["proto"].(string); ok {
												if port, ok := value["port"].(string); ok {
													tunnelsBuffer.WriteString(fmt.Sprintf("  %s:\r\n", name))
													tunnelsBuffer.WriteString("    proto:\r\n")
													tunnelsBuffer.WriteString(fmt.Sprintf("      %s: %s\r\n", proto, port))
												}
											}
										}
									}
								}
							case "hosts":
								hostsMaps = make(map[string]string)
								for _, value := range value.([]interface{}) {
									if value, ok := value.(map[string]interface{}); ok {
										if domain, ok := value["domain"].(string); ok {
											if ip, ok := value["ip"].(string); ok {
												hostsMaps[domain] = ip
											}
										}
									}
								}
							}
						}
					}
					log.Info(configBuffer.String())
					log.Info(tunnelsBuffer.String())
					if 0 < configBuffer.Len() {
						if f, err := os.Create(config); nil == err {
							defer f.Close()
							f.WriteString(configBuffer.String())
							if 0 < tunnelsBuffer.Len() {
								f.WriteString("\r\n")
								f.WriteString(tunnelsBuffer.String())
							}
							ret = true
						}
					}
					if 0 < len(hostsMaps) {
						if f, err := os.Create(hosts); nil == err {
							defer f.Close()
							f.WriteString("127.0.0.1\t\tlocalhost\n")
							for key, value := range hostsMaps {
								log.Info("%s -> %s", key, value)
								f.WriteString(fmt.Sprintf("%s\t\t%s\n", value, key))
							}
						}
					}
				}
			}
		}
	}
	return
}

func downloadConfigFromServer(jconfig string, config string, hosts string) {
	if url, ok := getConfigUpdateUrl(jconfig); ok && 0 < len(url) {
		log.Info("downloadConfigFromServer url: %s", url)
		for i := 0; i < 10; i++ {
			if data, ok := getJsonFromServer(url); ok {
				if ok := parseJsonToConfig(data, config, hosts); ok {
					break
				}
			} else {
				log.Info("Timeout")
			}
		}

	}
	time.Sleep(1 * time.Second)
}

func updateCheckFunc(jconfig string) {
	if url, ok := getConfigUpdateUrl(jconfig); ok && 0 < len(url) {
		url += "&mode=update"
		log.Info("updateCheckFunc url: %s", url)
		for {
			if data, ok := getJsonFromServer(url); ok {
				var maps map[string]interface{}
				if err := json.Unmarshal(data, &maps); nil == err {
					if status, ok := maps["status"].(bool); ok && status {
						time.Sleep(1 * time.Second)
						os.Exit(0)
					}
				}
			}
			time.Sleep(30 * time.Second)
		}
	}
}
